package com.sunyard.utils;

import cn.hutool.core.convert.Convert;
import com.sunyard.entity.ServiceSn;
import com.sunyard.exception.IllegalOperationException;
import lombok.extern.slf4j.Slf4j;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;

/**
 * serviceSn工具类
 *
 * @author DUANYUBIN
 * @date 2021-05-07 20:35
 **/
@Slf4j
public class ServiceSnUtils {

    /**
     * 毫秒内序列(0~4095)
     */
    private long sequence = 0L;

    /**
     * 上次时间戳，初始值为负数
     */
    private long lastTimestamp = -1L;

    /**
     * 秒内最大序列
     */
    private final long maxSequence = 1000L;

    private String machineSn;

//    private ServiceSn serviceSn;

    /**
     * 单例 私有化构造方法
     */
    private ServiceSnUtils() {
    }

    private ServiceSnUtils(String machineSn) {
        this.machineSn = machineSn;
//        this.serviceSn = new ServiceSn();
    }

    /**
     * 懒汉式单例
     */
    private static ServiceSnUtils SERVICE_SN_UTILS = null;

    /**
     * 初始化
     */
    public static synchronized void setInstance(String machineSn) {
        if (SERVICE_SN_UTILS == null) {
            SERVICE_SN_UTILS = new ServiceSnUtils(machineSn);
        }
    }

    /**
     * 初始化
     */
    public static synchronized ServiceSnUtils getInstance() {
        if (SERVICE_SN_UTILS == null) {
            throw new IllegalOperationException("单例初始化失败");
        }
        return SERVICE_SN_UTILS;
    }

    /**
     * 获得下一个ID (该方法是线程安全的)
     *
     * @return SnowflakeId
     */
    public synchronized ServiceSn nextId() {
        ServiceSn serviceSn = new ServiceSn();
        long timestamp = timeGen();

        //获取当前时间戳如果小于上次时间戳，则表示时间戳获取出现异常
        if (timestamp < lastTimestamp) {
            log.error("clock is moving backwards.  Rejecting requests until : {}.", lastTimestamp);
            throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds",
                    lastTimestamp - timestamp));
        }

        //获取当前时间戳如果等于上次时间戳（同一秒内），则在序列号加一；否则序列号赋值为0，从0开始。
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1);
            // 秒内序列溢出
            if (sequence == maxSequence) {
                sequence = 0L;
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }

        //将上次时间戳值刷新
        lastTimestamp = timestamp;

        /**
         * 返回结果：
         * (timestamp - twepoch) << timestampLeftShift) 表示将时间戳减去初始时间戳，再左移相应位数
         * (datacenterId << datacenterIdShift) 表示将数据id左移相应位数
         * (workerId << workerIdShift) 表示将工作id左移相应位数
         * | 是按位或运算符，例如：x | y，只有当x，y都为0的时候结果才为0，其它情况结果都为1。
         * 因为个部分只有相应位上的值有意义，其它位上都是0，所以将各部分的值进行 | 运算就能得到最终拼接好的id
         */
        serviceSn.setRandomNumber(machineSn + StringUtil.addLeftZeroForNum(Convert.toStr(sequence), 3));
        serviceSn.setTimestamp(String.valueOf(timestamp));
        serviceSn.setData(DateTimeFormatter.ofPattern(DateUtil.YYYYMMDDHHMMSS).format(LocalDateTime.ofInstant(Instant.ofEpochSecond(timestamp), ZoneId.systemDefault())));
        return serviceSn;
    }

    /**
     * 阻塞到下一个毫秒，直到获得新的时间戳
     *
     * @param lastTimestamp 上次生成ID的时间截
     * @return 当前时间戳
     */
    private long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    /**
     * 返回以毫秒为单位的当前时间
     *
     * @return 当前时间(毫秒)
     */
    private long timeGen() {
//        return System.currentTimeMillis() / 1000;
        return Instant.now().getEpochSecond();
    }
}
